/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.file;

import static com.android.dx.rop.annotation.AnnotationVisibility.SYSTEM;

import java.util.ArrayList;

import com.android.dx.rop.annotation.Annotation;
import com.android.dx.rop.annotation.NameValuePair;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstAnnotation;
import com.android.dx.rop.cst.CstArray;
import com.android.dx.rop.cst.CstInteger;
import com.android.dx.rop.cst.CstKnownNull;
import com.android.dx.rop.cst.CstMethodRef;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeList;

/**
 * Utility class for dealing with annotations.
 */
public final class AnnotationUtils {

	/** {@code non-null;} type for {@code AnnotationDefault} annotations */
	private static final CstType ANNOTATION_DEFAULT_TYPE = CstType.intern(Type
			.intern("Ldalvik/annotation/AnnotationDefault;"));

	/** {@code non-null;} type for {@code EnclosingClass} annotations */
	private static final CstType ENCLOSING_CLASS_TYPE = CstType.intern(Type
			.intern("Ldalvik/annotation/EnclosingClass;"));

	/** {@code non-null;} type for {@code EnclosingMethod} annotations */
	private static final CstType ENCLOSING_METHOD_TYPE = CstType.intern(Type
			.intern("Ldalvik/annotation/EnclosingMethod;"));

	/** {@code non-null;} type for {@code InnerClass} annotations */
	private static final CstType INNER_CLASS_TYPE = CstType.intern(Type
			.intern("Ldalvik/annotation/InnerClass;"));

	/** {@code non-null;} type for {@code MemberClasses} annotations */
	private static final CstType MEMBER_CLASSES_TYPE = CstType.intern(Type
			.intern("Ldalvik/annotation/MemberClasses;"));

	/** {@code non-null;} type for {@code Signature} annotations */
	private static final CstType SIGNATURE_TYPE = CstType.intern(Type
			.intern("Ldalvik/annotation/Signature;"));

	/** {@code non-null;} type for {@code Throws} annotations */
	private static final CstType THROWS_TYPE = CstType.intern(Type
			.intern("Ldalvik/annotation/Throws;"));

	/** {@code non-null;} the UTF-8 constant {@code "accessFlags"} */
	private static final CstString ACCESS_FLAGS_STRING = new CstString(
			"accessFlags");

	/** {@code non-null;} the UTF-8 constant {@code "name"} */
	private static final CstString NAME_STRING = new CstString("name");

	/** {@code non-null;} the UTF-8 constant {@code "value"} */
	private static final CstString VALUE_STRING = new CstString("value");

	/**
	 * This class is uninstantiable.
	 */
	private AnnotationUtils() {
		// This space intentionally left blank.
	}

	/**
	 * Constructs a standard {@code AnnotationDefault} annotation.
	 * 
	 * @param defaults
	 *            {@code non-null;} the defaults, itself as an annotation
	 * @return {@code non-null;} the constructed annotation
	 */
	public static Annotation makeAnnotationDefault(Annotation defaults) {
		Annotation result = new Annotation(ANNOTATION_DEFAULT_TYPE, SYSTEM);

		result.put(new NameValuePair(VALUE_STRING, new CstAnnotation(defaults)));
		result.setImmutable();
		return result;
	}

	/**
	 * Constructs a standard {@code EnclosingClass} annotation.
	 * 
	 * @param clazz
	 *            {@code non-null;} the enclosing class
	 * @return {@code non-null;} the annotation
	 */
	public static Annotation makeEnclosingClass(CstType clazz) {
		Annotation result = new Annotation(ENCLOSING_CLASS_TYPE, SYSTEM);

		result.put(new NameValuePair(VALUE_STRING, clazz));
		result.setImmutable();
		return result;
	}

	/**
	 * Constructs a standard {@code EnclosingMethod} annotation.
	 * 
	 * @param method
	 *            {@code non-null;} the enclosing method
	 * @return {@code non-null;} the annotation
	 */
	public static Annotation makeEnclosingMethod(CstMethodRef method) {
		Annotation result = new Annotation(ENCLOSING_METHOD_TYPE, SYSTEM);

		result.put(new NameValuePair(VALUE_STRING, method));
		result.setImmutable();
		return result;
	}

	/**
	 * Constructs a standard {@code InnerClass} annotation.
	 * 
	 * @param name
	 *            {@code null-ok;} the original name of the class, or
	 *            {@code null} to represent an anonymous class
	 * @param accessFlags
	 *            the original access flags
	 * @return {@code non-null;} the annotation
	 */
	public static Annotation makeInnerClass(CstString name, int accessFlags) {
		Annotation result = new Annotation(INNER_CLASS_TYPE, SYSTEM);
		Constant nameCst = (name != null) ? name : CstKnownNull.THE_ONE;

		result.put(new NameValuePair(NAME_STRING, nameCst));
		result.put(new NameValuePair(ACCESS_FLAGS_STRING, CstInteger
				.make(accessFlags)));
		result.setImmutable();
		return result;
	}

	/**
	 * Constructs a standard {@code MemberClasses} annotation.
	 * 
	 * @param types
	 *            {@code non-null;} the list of (the types of) the member
	 *            classes
	 * @return {@code non-null;} the annotation
	 */
	public static Annotation makeMemberClasses(TypeList types) {
		CstArray array = makeCstArray(types);
		Annotation result = new Annotation(MEMBER_CLASSES_TYPE, SYSTEM);
		result.put(new NameValuePair(VALUE_STRING, array));
		result.setImmutable();
		return result;
	}

	/**
	 * Constructs a standard {@code Signature} annotation.
	 * 
	 * @param signature
	 *            {@code non-null;} the signature string
	 * @return {@code non-null;} the annotation
	 */
	public static Annotation makeSignature(CstString signature) {
		Annotation result = new Annotation(SIGNATURE_TYPE, SYSTEM);

		/*
		 * Split the string into pieces that are likely to be common across many
		 * signatures and the rest of the file.
		 */

		String raw = signature.getString();
		int rawLength = raw.length();
		ArrayList<String> pieces = new ArrayList<String>(20);

		for (int at = 0; at < rawLength; /* at */) {
			char c = raw.charAt(at);
			int endAt = at + 1;
			if (c == 'L') {
				// Scan to ';' or '<'. Consume ';' but not '<'.
				while (endAt < rawLength) {
					c = raw.charAt(endAt);
					if (c == ';') {
						endAt++;
						break;
					} else if (c == '<') {
						break;
					}
					endAt++;
				}
			} else {
				// Scan to 'L' without consuming it.
				while (endAt < rawLength) {
					c = raw.charAt(endAt);
					if (c == 'L') {
						break;
					}
					endAt++;
				}
			}

			pieces.add(raw.substring(at, endAt));
			at = endAt;
		}

		int size = pieces.size();
		CstArray.List list = new CstArray.List(size);

		for (int i = 0; i < size; i++) {
			list.set(i, new CstString(pieces.get(i)));
		}

		list.setImmutable();

		result.put(new NameValuePair(VALUE_STRING, new CstArray(list)));
		result.setImmutable();
		return result;
	}

	/**
	 * Constructs a standard {@code Throws} annotation.
	 * 
	 * @param types
	 *            {@code non-null;} the list of thrown types
	 * @return {@code non-null;} the annotation
	 */
	public static Annotation makeThrows(TypeList types) {
		CstArray array = makeCstArray(types);
		Annotation result = new Annotation(THROWS_TYPE, SYSTEM);
		result.put(new NameValuePair(VALUE_STRING, array));
		result.setImmutable();
		return result;
	}

	/**
	 * Converts a {@link TypeList} to a {@link CstArray}.
	 * 
	 * @param types
	 *            {@code non-null;} the type list
	 * @return {@code non-null;} the corresponding array constant
	 */
	private static CstArray makeCstArray(TypeList types) {
		int size = types.size();
		CstArray.List list = new CstArray.List(size);

		for (int i = 0; i < size; i++) {
			list.set(i, CstType.intern(types.getType(i)));
		}

		list.setImmutable();
		return new CstArray(list);
	}
}
